﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using Legend.Collections;
using System.Web;
using System.Collections.Specialized;
using System.Runtime.Serialization;

namespace Legend.Web.QueryString.Tests.QueryStringManagerTests
{
    public class QueryStringManagerTestCase
    {
        protected QueryStringManager manager;
        protected MockSerializer mockSerializer;

        [SetUp]
        public void SetUp()
        {
            this.mockSerializer = new MockSerializer();
            this.manager = new QueryStringManager(this.mockSerializer);

            this.OnSetUp();
        }

        protected virtual void OnSetUp()
        {
            // Overridable to add setup logic.
        }

        
        protected class MockSerializer
            : IQueryStringSerializer
        {
            public string SerializedQuery;
            public string ParameterUsedInCallToParse;
            public Dictionary<string, string> ParameterUsedInCallToSerialize;
            public Dictionary<string, string> ParseReturnValue;

            

            public IDictionary<string, string> Parse(string queryString)
            {
                this.ParameterUsedInCallToParse = queryString;
                return this.ParseReturnValue;
            }

            public string Serialize(IDictionary<string, string> keyValuePairs)
            {
                if (ParameterUsedInCallToSerialize != null) throw new InvalidOperationException();

                this.ParameterUsedInCallToSerialize = keyValuePairs.ToDictionary(k => k.Key, v => v.Value);
                return this.SerializedQuery;
            }


            
        }

        [DataContract]
        protected class AttributedQueryClass
        {
            [DataMember]
            public int Foo;

            [DataMember]
            public string Bar { get; set; }
        }

        protected class ParameterAccessorImplementor
            : IParameterAccessor
        {

            public Dictionary<string, string> Values = new Dictionary<string, string>();

            public void ApplyParameterValue(string parameterName, string value)
            {
                this.Values.Add(parameterName, value);
            }

            public IDictionary<string, string> GetAllParametersAndValues()
            {
                return this.Values;
            }
        }
        
    }

    [TestFixture]
    public class Render
        : QueryStringManagerTestCase
    {
        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void should_throw_exception_when_query_object_is_null()
        {
            this.manager.Render(null);
        }

        [Test]
        public void should_send_member_values_from_attributed_object_to_serializer()
        {
            this.manager.Render(new AttributedQueryClass() { Foo = 1, Bar = "2" });

            var call = this.mockSerializer.ParameterUsedInCallToSerialize;
            
            Assert.AreEqual(2, call.Count);
            Assert.AreEqual("1", call["Foo"]);
            Assert.AreEqual("2", call["Bar"]);
        }

        [Test]
        public void should_send_values_from_IParameterAccessor_to_serializer()
        {
            var query = new ParameterAccessorImplementor();
            query.Values.Add("a", "a");
            query.Values.Add("b", "b");

            this.manager.Render(query);

            var call = this.mockSerializer.ParameterUsedInCallToSerialize;
            Assert.AreEqual(2, call.Count);
            Assert.AreEqual("a", call["a"]);
            Assert.AreEqual("b", call["b"]);
        }

        
    }

    [TestFixture]
    public class Apply
        : QueryStringManagerTestCase
    {
        protected override void OnSetUp()
        {
            base.OnSetUp();
            this.mockSerializer.ParseReturnValue = new Dictionary<string, string>() {
                {"Foo", "1"},
                {"Bar", "a"}
            };
        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void should_throw_exception_when_query_object_is_null()
        {
            this.manager.ApplyQueryStringValuesToQueryObject("Foo=bar", null);
        }

        [Test]
        public void applies_values_to_attributed_object()
        {
            var queryObject = new AttributedQueryClass();
            
            this.manager.ApplyQueryStringValuesToQueryObject("values from mock serializer are used", queryObject);
            
            Assert.AreEqual(1, queryObject.Foo);
            Assert.AreEqual("a", queryObject.Bar);
        }

        [Test]
        public void applies_values_to_IParameterAccessor()
        {
            var accessor = new ParameterAccessorImplementor();
            this.manager.ApplyQueryStringValuesToQueryObject("values from mock serializer are used", accessor);

            Assert.AreEqual(2, accessor.Values.Count);
            Assert.AreEqual("1", accessor.Values["Foo"]);
            Assert.AreEqual("a", accessor.Values["Bar"]);
        }

        [Test]
        public void should_strip_query_string_from_url()
        {
            var accessor = new ParameterAccessorImplementor();
            this.manager.ApplyQueryStringValuesToQueryObject("http://foo.com/?Foo=1&Bar=a", accessor);

            Assert.AreEqual("Foo=1&Bar=a", this.mockSerializer.ParameterUsedInCallToParse);
        }

        [Test]
        public void should_call_serializer_with_empty_string_if_no_query_exists_on_url()
        {
            var accessor = new ParameterAccessorImplementor();
            this.manager.ApplyQueryStringValuesToQueryObject("http://foo.com/?", accessor);

            Assert.AreEqual(string.Empty, this.mockSerializer.ParameterUsedInCallToParse);
        }

        [Test]
        public void should_treat_string_with_no_question_mark_as_query_string()
        {
            var accessor = new ParameterAccessorImplementor();
            this.manager.ApplyQueryStringValuesToQueryObject("Foo=bar", accessor);

            Assert.AreEqual("Foo=bar", this.mockSerializer.ParameterUsedInCallToParse);
        }
    }

    [TestFixture]
    public class Apply_NameValueCollection_overload
        : QueryStringManagerTestCase
    {
        private NameValueCollection queryString;
        protected override void OnSetUp()
        {
            base.OnSetUp();
            this.queryString = new NameValueCollection(){
                {"Foo", "1"},
                {"Bar", "a"}
            };
               
        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void should_throw_exception_when_query_object_is_null()
        {
            this.manager.ApplyQueryStringValuesToQueryObject(this.queryString, null);
        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void should_throw_exception_when_query_string_is_null()
        {
            this.manager.ApplyQueryStringValuesToQueryObject((NameValueCollection)null, new AttributedQueryClass());
        }

        [Test]
        public void applies_values_to_attributed_object()
        {
            var queryObject = new AttributedQueryClass();

            this.manager.ApplyQueryStringValuesToQueryObject(this.queryString, queryObject);

            Assert.AreEqual(1, queryObject.Foo);
            Assert.AreEqual("a", queryObject.Bar);
        }

        [Test]
        public void applies_values_to_IParameterAccessor()
        {
            var accessor = new ParameterAccessorImplementor();
            this.manager.ApplyQueryStringValuesToQueryObject(this.queryString, accessor);

            Assert.AreEqual(2, accessor.Values.Count);
            Assert.AreEqual("1", accessor.Values["Foo"]);
            Assert.AreEqual("a", accessor.Values["Bar"]);
        }
    }

    [TestFixture]
    public class Append_uri_overload
        : QueryStringManagerTestCase
    {
        private object query = new FooQuery();

        protected override void OnSetUp()
        {
            this.mockSerializer.SerializedQuery = "Foo=bar";
        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void should_throw_exception_when_uri_is_null()
        {
            this.manager.Append((Uri)null, new object());
        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void should_throw_exception_when_query_is_null()
        {
            this.manager.Append(new Uri("http://foo.com/"), null);
        }

        [Test]
        public void should_return_new_uri_containing_query()
        {
            var result = this.manager.Append(new Uri("http://foo.com/"), this.query);
            Assert.AreEqual("?Foo=bar", result.Query);
        }

        [Test]
        public void should_return_url_only_when_rendered_query_is_empty()
        {
            this.mockSerializer.SerializedQuery = string.Empty;
            var result = manager.Append(new Uri("http://foo.com/"), this.query);

            Assert.AreEqual("http://foo.com/", result.AbsoluteUri);
        }

        [Test]
        public void should_return_url_only_when_rendered_query_is_null()
        {
            this.mockSerializer.SerializedQuery = null;
            var result = manager.Append(new Uri("http://foo.com/"), this.query);

            Assert.AreEqual("http://foo.com/", result.AbsoluteUri);
        }

        [Test, ExpectedException(typeof(ArgumentException))]
        public void should_throw_exception_when_uri_contains_query()
        {
            this.manager.Append(new Uri("http://foo.com/?foo=bar"), this.query);
        }

        [DataContract]
        private class FooQuery { }
    }

    [TestFixture]
    public class Append
        : QueryStringManagerTestCase 
    {
        private object query = new FooQuery();

        protected override void OnSetUp()
        {
            this.mockSerializer.SerializedQuery = "Foo=bar";
        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void should_throw_exception_when_query_is_null()
        {
            manager.Append("http://www.foo.com/", null);
        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void should_throw_exception_when_url_is_null()
        {
            manager.Append((string)null, this.query);
        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void should_throw_exception_when_url_is_empty_string()
        {
            manager.Append(string.Empty, this.query);
        }

        [Test]
        public void should_return_concatenated_url_and_query()
        {
            var result = manager.Append("http://www.foo.com/index.html", this.query);
            Assert.AreEqual("http://www.foo.com/index.html?Foo=bar", result);
        }

        [Test]
        public void should_return_url_only_when_rendered_query_is_empty()
        {
            this.mockSerializer.SerializedQuery = string.Empty;
            var result = manager.Append("http://foo.com/", this.query);

            Assert.AreEqual("http://foo.com/", result);
        }

        [Test]
        public void should_return_url_only_when_rendered_query_is_null()
        {
            this.mockSerializer.SerializedQuery = null;
            var result = manager.Append("http://foo.com/", this.query);

            Assert.AreEqual("http://foo.com/", result);
        }

        [DataContract]
        private class FooQuery { }
    }

    [TestFixture]
    public class Constructor
    {
        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void should_throw_exception_when_serializer_is_null()
        {
            new QueryStringManager(null);
        }
    }
}
